﻿using System;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Xml.XPath;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;
using System.IO.Packaging;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Drawing;
using Microsoft.Office.Interop.Word;

using Microsoft.Ddue.Tools;

namespace component
{
    public partial class DocxBuilderComponent : BuildComponent
    {
        public static Numbering numbering = new Numbering();
        
        //количество топиков
        public static int countFiles = 0;

        //название документации
        public static string nameDoc = "";
        //название конвертируемой документации
        public static string convertFile = "";
        //путь, куда сохраняем документацию
        public static string outputPath = "";
        //путь к файлу проекта
        public static string basePath = "";
        //путь к файлу contentLayout
        public static string contentLayout = "";

        public static string topicID = ""; //идентификатор топика
        public static List<string> tokenFiles = new List<string>(); //список файлов tokens
        public static Dictionary<string, string> tokenIdAndName = new Dictionary<string, string>(); //список файлов tokens с идентификатором и названием
        public static Dictionary<string, string> topicTitleAndId = new Dictionary<string, string>(); //список топиков с идентификатором и названием
        public static Dictionary<string, string> mediaIdandPath = new Dictionary<string, string>(); //список media файлов с путями
        public static Dictionary<string, ImagePart> mediaIdAndImagePartName = new Dictionary<string, ImagePart>(); //список media файлов с идентификатором media файла и названием ImagePart документа
        public static Dictionary<string, string> mediaIdAndImagePartId = new Dictionary<string, string>(); //список media файлов с идентификатором media файла и идентификатором ImagePart документа
        public static Dictionary<string, long[]> mediaIdAndHeightWidth = new Dictionary<string, long[]>(); //список media файлов с идентификатором media файла и значений высоты и ширины media файла

        //флаг, если гинерируем содержание
        static bool isContent = false;
        //флаг, если конвертируем в pdf
        bool isPDF = false;
        //флаг, если конвертируем в xps
        bool isXPS = false;
        //название стиля
        public static string styleName = "";

        public DocxBuilderComponent(BuildAssembler assembler, XPathNavigator configuration)
            : base(assembler, configuration)
        {
            //считываем название стиля
            XPathNodeIterator style_nodes = configuration.Select("style");
            foreach (XPathNavigator style_node in style_nodes)
            {
                styleName = style_node.GetAttribute("name", String.Empty);
                if (String.IsNullOrEmpty(styleName))
                    WriteMessage(MessageLevel.Error, "The style /@name attribute must specify a name style.");
            }

            //считываем путь к файлу contentLayout, с которого будем считывать топики, для построения содержания
            XPathNodeIterator contentLayout_nodes = configuration.Select("contentLayout");
            foreach (XPathNavigator contentLayout_node in contentLayout_nodes)
            {
                contentLayout = contentLayout_node.GetAttribute("file", String.Empty);
                if (String.IsNullOrEmpty(contentLayout))
                    WriteMessage(MessageLevel.Error, "The contentLayout /@file attribute must specify a name path to file.");
            }

            //считываем константные странички (содержание, титульный лист)
            XPathNodeIterator elementsDoc_nodes = configuration.Select("elementsDoc");
            foreach (XPathNavigator elementsDoc_node in elementsDoc_nodes)
            {
                if (elementsDoc_node.GetAttribute("content", String.Empty) == "true")
                    isContent = true;
                if (String.IsNullOrEmpty(elementsDoc_node.GetAttribute("content", String.Empty)))
                    WriteMessage(MessageLevel.Error, "The elementsDoc /@content attribute must specify a true or false.");
            }

            //считываем данные для конвертирования 
            XPathNodeIterator convert_nodes = configuration.Select("convert");
            foreach (XPathNavigator convert_node in convert_nodes)
            {
                if (convert_node.GetAttribute("PDF", String.Empty) == "true")
                    isPDF = true;
                if (convert_node.GetAttribute("XPS", String.Empty) == "true")
                    isXPS = true;
                if (String.IsNullOrEmpty(convert_node.GetAttribute("PDF", String.Empty)))
                    WriteMessage(MessageLevel.Error, "The convert /@PDF attribute must specify a true or false.");
                if (String.IsNullOrEmpty(convert_node.GetAttribute("XPS", String.Empty)))
                    WriteMessage(MessageLevel.Error, "The convert /@XPS attribute must specify a true or false.");
            }

            //считываем название документации
            XPathNodeIterator doc_nodes = configuration.Select("documentation");
            foreach (XPathNavigator doc_node in doc_nodes)
            {
                string name = doc_node.GetAttribute("name", String.Empty);
                nameDoc = name + ".docx";
                convertFile = name;
                if (String.IsNullOrEmpty(name))
                    WriteMessage(MessageLevel.Error, "The documentation /@name attribute must specify a name.");
            }

            //считываем путь к файлу проекта и путь, куда будем сохранять документацию 
            XPathNodeIterator path_nodes = configuration.Select("path");
            foreach (XPathNavigator path_node in path_nodes)
            {
                outputPath = path_node.GetAttribute("outputPath", String.Empty);
                if (String.IsNullOrEmpty(nameDoc))
                    WriteMessage(MessageLevel.Error, "The path /@outputPath attribute must specify a path.");
                basePath = path_node.GetAttribute("basePath", String.Empty) + "\\";
                if (String.IsNullOrEmpty(nameDoc))
                    WriteMessage(MessageLevel.Error, "The path /@basePath attribute must specify a path.");
            }

            //формируем список файлов tokens
            XPathNodeIterator content_nodes = configuration.Select("tokenFile");
            foreach (XPathNavigator content_node in content_nodes)
            {
                string sharedContentFiles = content_node.GetAttribute("file", String.Empty);
                if (String.IsNullOrEmpty(sharedContentFiles))
                    WriteMessage(MessageLevel.Error, "The content/@file attribute must specify a path.");

                string wildcardPath = Environment.ExpandEnvironmentVariables(sharedContentFiles);
                if (String.IsNullOrEmpty(wildcardPath))
                    WriteMessage(MessageLevel.Error, "The content/@file attribute specifies an empty string.");

                WriteMessage(MessageLevel.Info, String.Format("Searching for files that match '{0}'.", wildcardPath));
                string directoryPart = Path.GetDirectoryName(wildcardPath);
                if (String.IsNullOrEmpty(directoryPart))
                    directoryPart = Environment.CurrentDirectory;
                directoryPart = Path.GetFullPath(directoryPart);
                string filePart = Path.GetFileName(wildcardPath);
                string[] files = Directory.GetFiles(directoryPart, filePart);
                foreach (string file in files)
                {
                    tokenFiles.Add(file);
                }
                WriteMessage(MessageLevel.Info, String.Format("Found {0} files in {1}.", files.Length, sharedContentFiles));
            }

            //формируем список топиков с идентификаторами и названиями
            XPathNodeIterator data_nodes = configuration.Select("data");
            foreach (XPathNavigator data_node in data_nodes)
            {
                string files = data_node.GetAttribute("files", String.Empty);
                if (String.IsNullOrEmpty(files)) WriteMessage(MessageLevel.Error, "Each data element must have a \"files\" attribute specifying which files to index.");
                files = Environment.ExpandEnvironmentVariables(files);
                WriteMessage(MessageLevel.Info, String.Format("Searching for files that match '{0}'.", files));

                string directory_part = Path.GetDirectoryName(files);
                if (String.IsNullOrEmpty(directory_part))
                    directory_part = Environment.CurrentDirectory;
                directory_part = Path.GetFullPath(directory_part);
                WriteMessage(MessageLevel.Info, String.Format(directory_part));

                string file_part = Path.GetFileName(files);
                string[] massFiles = Directory.GetFiles(directory_part, file_part);
                countFiles = massFiles.Length;
                foreach (string file in massFiles)
                {
                    XmlDocument fileXmlCmp = new XmlDocument();
                    fileXmlCmp.Load(file);
                    string topicId = fileXmlCmp.SelectSingleNode("//@id").InnerText;
                    string topicName = fileXmlCmp.SelectSingleNode("//topic/title").InnerText;
                    topicTitleAndId.Add(topicId, topicName);
                }
            }

            //формируем список media файлов
            XmlDocument mediaContent = new XmlDocument();
            mediaContent.Load(outputPath + @"\Working\_MediaContent_.xml");
            XmlNode rootMedia = mediaContent.DocumentElement;
            if (rootMedia.HasChildNodes)
            {
                XmlNodeList listId = mediaContent.SelectNodes("//@id");
                XmlNodeList listImageName = mediaContent.SelectNodes("//@file");
                for (int i = 0; i < listId.Count; i++)
                    mediaIdandPath.Add(listId[i].InnerText, outputPath + @"\Working\Media\" + listImageName[i].InnerText);
            }
        }

        public static bool isFirst = false;

        public override void Apply(XmlDocument document, string key)
        {
            //записываем идентификатор топика
            topicID = key;
            //уменьшаем количество файлов
            countFiles--;
            //если файл существует, дописываем в него данные
            if (File.Exists(outputPath + nameDoc))
            {
                using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(outputPath + nameDoc, true))
                {
                    Body body = wordDocument.MainDocumentPart.Document.Body;
                    parser(document, ref body);
                    if (countFiles == 0)
                    {
                        wordDocument.MainDocumentPart.AddNewPart<NumberingDefinitionsPart>("rId1");
                        for (int i = 1; i <= nemberIdMax; i++)
                        {
                            numbering.Append(new NumberingInstance(
                                new AbstractNumId() { Val = i - 1 }) { NumberID = i });
                        }
                        wordDocument.MainDocumentPart.NumberingDefinitionsPart.Numbering = numbering;
                    }
                    wordDocument.MainDocumentPart.Document.Save();
                }
            }

            //если файл не существует, создаем новый и записываем в него данные
            else
            {
                isFirst = true;
                using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(outputPath + nameDoc, WordprocessingDocumentType.Document))
                {
                    //добавляем уровни с 0 по 8 и начальное значение устанавливаем в 1, для формирования нумерации 
                    for (int i = 0; i < 9; i++)
                        levelAndStartValue.Add(i, 1);

                    MainDocumentPart mainPart = wordDocument.AddMainDocumentPart();
                    mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
                    Body body = new Body();

                    //если список token файлов не пустой
                    if (tokenFiles.Count != 0)
                    {
                        //составляем список файлов tokens с идентификаторами и названиями
                        foreach (string token in tokenFiles)
                        {
                            XmlDocument tokenXml = new XmlDocument();
                            tokenXml.Load(token);
                            XmlNode root = tokenXml.DocumentElement;

                            if (root.HasChildNodes)
                            {
                                XmlNamespaceManager xnsManager = new XmlNamespaceManager(tokenXml.NameTable);
                                xnsManager.AddNamespace("ddue", "http://ddue.schemas.microsoft.com/authoring/2003/5");
                                xnsManager.AddNamespace("xlink", "http://www.w3.org/1999/xlink");

                                XmlNodeList listItem = root.SelectNodes("item", xnsManager);

                                foreach (XmlNode item in listItem)
                                {
                                    string itemId = item.Attributes["id"].InnerText;
                                    foreach (XmlNode element in item)
                                    {
                                        if (element.NodeType == XmlNodeType.Text)
                                        {
                                            tokenIdAndName.Add(itemId, element.InnerText);
                                            break;
                                        }

                                        if (element.NodeType == XmlNodeType.Element && element.Name == "ddue:externalLink")
                                        {
                                            string linkText = element.SelectSingleNode("ddue:linkText", xnsManager).InnerText;
                                            string linkUri = element.SelectSingleNode("ddue:linkUri", xnsManager).InnerText;
                                            tokenIdAndName.Add(itemId, "#hiperlink#" + linkText);
                                            mainPart.AddHyperlinkRelationship(new System.Uri(linkUri, System.UriKind.Absolute), true, itemId);
                                            break;
                                        }
                                    }
                                }

                            }
                        }
                    }

                    //если список media файлов не пустой
                    if (mediaIdandPath.Count != 0)
                    {
                        foreach (string id in mediaIdandPath.Keys)
                        {
                            mediaIdAndImagePartName.Add(id, mainPart.AddImagePart(ImagePartType.Jpeg));
                            using (FileStream stream = new FileStream(mediaIdandPath[id], FileMode.Open))
                                mediaIdAndImagePartName[id].FeedData(stream);
                            mediaIdAndImagePartId.Add(id, mainPart.GetIdOfPart(mediaIdAndImagePartName[id]));
                            Bitmap bit = new Bitmap(mediaIdandPath[id]);
                            long[] HeightWidth = { bit.Height, bit.Width };
                            mediaIdAndHeightWidth.Add(id, HeightWidth);
                        }

                    }

                    if (isContent)
                        Components.createConstElements.createContent(ref body, topicTitleAndId, contentLayout);

                    parser(document, ref body);

                    if (countFiles == 0)
                    {
                        mainPart.AddNewPart<NumberingDefinitionsPart>("rId1");
                        for (int i = 1; i <= nemberIdMax; i++)
                        {
                            numbering.Append(new NumberingInstance(
                                new AbstractNumId() { Val = i - 1 }) { NumberID = i });
                        }
                        mainPart.NumberingDefinitionsPart.Numbering = numbering;
                    }

                    StyleDefinitionsPart styleDefinitionsPart = mainPart.AddNewPart<StyleDefinitionsPart>("rId2");
                    component.StyleDefinitions.GenerateStyleDefinitionsPartContent(styleDefinitionsPart, styleName);
                    mainPart.Document.Append(body);
                    mainPart.Document.Save();
                }
            }

            if (countFiles == 0)
            {
                if (isXPS)
                    ConvertDocument(outputPath + nameDoc, outputPath + convertFile, WdExportFormat.wdExportFormatXPS);
                if(isPDF)
                    ConvertDocument(outputPath + nameDoc, outputPath + convertFile, WdExportFormat.wdExportFormatPDF);
                
            }
        }

        public static void parser(XmlDocument doc, ref Body body)
        {
            foreach (XmlNode read in doc.FirstChild)
            {
                if (read.Name == "topic" && read.HasChildNodes)
                {
                    foreach (XmlNode element in read)
                    {
                        if (element.NodeType == XmlNodeType.Element)
                            whatTopic(element, ref body);
                    }
                }
            }
        }

        //узнаем какой топик
        public static void whatTopic(XmlNode topic, ref Body body)
        {
            switch (topic.Name)
            {
                case "developerConceptualDocument":
                    {
                        if (topic.HasChildNodes)
                        {
                            foreach(OpenXmlElement el in returnDeveloperConceptualElements(topic, isContent))
                                body.Append(el);
                        }
                    }
                    break;
                //case "developerErrorMessageDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerErrorMessageDocument(doc, ref body);
                //    }
                //    break;
                case "developerGlossaryDocument":
                    {
                        if (topic.HasChildNodes)
                        {
                            foreach (OpenXmlElement el in returnDeveloperGlossaryElements(topic))
                                body.Append(el);
                        }
                    }
                    break;
                case "developerHowToDocument":
                    {
                        if (topic.HasChildNodes)
                        {
                            foreach (OpenXmlElement el in returnDeveloperHowToElements(topic))
                                body.Append(el);
                        }
                    }
                    break;
                //case "developerOrientationDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerOrientationDocument(doc, ref body);
                //    }
                //    break;
                //case "codeEntityDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            codeEntityDocument(doc, ref body);
                //    }
                //    break;
                //case "developerReferenceWithSyntaxDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerReferenceWithSyntaxDocument(doc, ref body);
                //    }
                //    break;
                //case "developerReferenceWithoutSyntaxDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerReferenceWithoutSyntaxDocument(doc, ref body);
                //    }
                //    break;
                //case "developerSampleDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerSampleDocument(doc, ref body);
                //    }
                //    break;
                //case "developerSDKTechnologyOverviewArchitectureDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerSDKTechnologyOverviewArchitectureDocument(doc, ref body);
                //    }
                //    break;
                //case "developerSDKTechnologyOverviewCodeDirectoryDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerSDKTechnologyOverviewCodeDirectoryDocument(doc, ref body);
                //    }
                //    break;
                //case "developerSDKTechnologyOverviewOrientationDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerSDKTechnologyOverviewOrientationDocument(doc, ref body);
                //    }
                //    break;
                //case "developerSDKTechnologyOverviewScenariosDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerSDKTechnologyOverviewScenariosDocument(doc, ref body);
                //    }
                //    break;
                //case "developerSDKTechnologyOverviewTechnologySummaryDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerSDKTechnologyOverviewTechnologySummaryDocument(doc, ref body);
                //    }
                //    break;
                //case "developerTroubleshootingDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerTroubleshootingDocument(doc, ref body);
                //    }
                //    break;
                //case "developerUIReferenceDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerUIReferenceDocument(doc, ref body);
                //    }
                //    break;
                //case "developerWalkthroughDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerWalkthroughDocument(doc, ref body);
                //    }
                //    break;
                //case "developerWhitePaperDocument":
                //    {
                //        if (doc.HasChildNodes)
                //            developerWhitePaperDocument(doc, ref body);
                //    }
                //    break;
                //case "developerXmlReference":
                //    {
                //        if (doc.HasChildNodes)
                //            developerXmlReference(doc, ref body);
                //    }
                //    break;
            }
        }
    }
}